package cn.zyq.demo.system.utils.redis.lock;

import cn.zyq.demo.system.config.consts.redis.RedisCommonKeys;
import cn.zyq.demo.system.enums.core.ResponseCodeEnum;
import cn.zyq.demo.system.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.Serializable;
import java.util.Collections;
import java.util.concurrent.Callable;

/**
 * 分布式事务锁
 */
@Slf4j
@Service
public class RedisDistributedLockService {
    @Resource(name = "redisScriptForIncr")
    private DefaultRedisScript<Long> redisScriptForIncr;
    @Resource(name = "redisTemplateForLua")
    private RedisTemplate<String, Serializable> redisTemplateForLua;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    private String getCacheKey(String lockKey) {
        return RedisCommonKeys.LOCK_KEY(lockKey);
    }

    public boolean lock(String lockKey, Long expireTime) {
//        Long value = this.redisTemplate.boundValueOps(lockKey).increment();
//        if (value == 1) {
//            this.redisTemplate.boundValueOps(lockKey).expireTime(expireTime, TimeUnit.MILLISECONDS);
//            return true;
//        }
        if (expireTime == null) {
            expireTime = 10000L;
        }
        Long count = redisTemplateForLua.execute(this.redisScriptForIncr, Collections.singletonList(this.getCacheKey(lockKey)), String.valueOf(expireTime));
        return count == 1;
    }

    public boolean lock(String lockKey) {
        return this.lock(lockKey, 10000L);
    }

    public void unlock(String lockKey) {
        this.redisTemplate.delete(this.getCacheKey(lockKey));
    }

    public Object executeAndBack(String lockKey, Callable<Object> callable) {
        return this.executeAndBack(lockKey, null, null, callable);

    }

    public Object executeAndBack(String lockKey, String tip, Callable<Object> callable) {
        return this.executeAndBack(lockKey, tip, null, callable);

    }

    public Object executeAndBack(String lockKey, Long expireTime, Callable<Object> callable) {
        return this.executeAndBack(lockKey, null, expireTime, callable);
    }

    public Object executeAndBack(String lockKey, String tip, Long expireTime, Callable<Object> callable) {
        if (this.lock(lockKey, expireTime)) {
            try {
                return callable.call();
            } catch (Exception e) {
                e.printStackTrace();
                throw new BusinessException(ResponseCodeEnum.ERROR);
            } finally {
                this.unlock(lockKey);
            }
        }
        if (StringUtils.isBlank(tip)) {
            throw new BusinessException(ResponseCodeEnum.BUSYNESS);
        } else {
            throw new BusinessException(ResponseCodeEnum.BUSYNESS, tip);
        }
    }

    public void execute(String lockKey, Callable<Object> callable) {
        this.execute(lockKey, null, null, callable);

    }

    public void execute(String lockKey, String tip, Callable<Object> callable) {
        this.execute(lockKey, tip, null, callable);

    }

    public void execute(String lockKey, Long expireTime, Callable<Object> callable) {
        this.executeAndBack(lockKey, null, expireTime, callable);
    }

    public void execute(String lockKey, String tip, Long expireTime, Callable<Object> callable) {
        if (this.lock(lockKey, expireTime)) {
            try {
                callable.call();
            } catch (Exception e) {
                e.printStackTrace();
                throw new BusinessException(ResponseCodeEnum.ERROR);
            } finally {
                this.unlock(lockKey);
            }
        }
        if (StringUtils.isBlank(tip)) {
            throw new BusinessException(ResponseCodeEnum.BUSYNESS);
        } else {
            throw new BusinessException(ResponseCodeEnum.BUSYNESS, tip);
        }
    }
}
